package com.zhangdi.mserver.searchcli.repository;

import com.zhangdi.mserver.searchcli.HighlightResponse;
import com.zhangdi.mserver.searchcli.HighlightResult;
import com.zhangdi.mserver.searchcli.MFunctionScore;
import com.zhangdi.mserver.searchcli.MSearchRequest;
import com.zhangdi.mserver.searchcli.enums.MatchType;
import com.zhangdi.mserver.searchcli.page.Page;
import com.zhangdi.mserver.searchcli.page.Pageable;
import com.zhangdi.mserver.searchcli.utils.BeanUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ClientBase<T> implements ClientApi<T> {
    RestHighLevelClient restHighLevelClient;

    public ClientBase(RestHighLevelClient restHighLevelClient) {
        this.restHighLevelClient = restHighLevelClient;
    }

    @Override
    public Page<T> search(MFunctionScore option, Class<T> tClass,
                          String[] includeField, String[] excludeField) {
        Page<T> pager = new Page<>();
        SearchSourceBuilder builder = sourceBuilder(option);
        builder.fetchSource(includeField, excludeField);
        SearchRequest searchRequest = new SearchRequest(option.getIndexName());
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            pager.setTotal((int) hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        pager.setNo(option.getPageable().getNo());
        pager.setSize(option.getPageable().getSize());
        pager.setList(result);

        return pager;
    }

    @Override
    public List<T> search(MSearchRequest c, Class<T> tClass, String indexName,
                          String[] includeField, String[] excludeField) {
        List<MSearchRequest> requests = new ArrayList<>();
        requests.add(c);
        SearchSourceBuilder builder = this.sourceBuilder(requests);
        builder.fetchSource(includeField, excludeField);

        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    @Override
    public Page<T> search(Pageable<List<MSearchRequest>> pageable, Class<T> tClass, String indexName,
                          String[] includeField, String[] excludeField) {
        Page<T> pager = new Page<>();

        SearchSourceBuilder builder = this.sourceBuilder(pageable);
        builder.fetchSource(includeField, excludeField);
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            pager.setTotal((int) hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        pager.setNo(pageable.getNo());
        pager.setSize(pageable.getSize());
        pager.setList(result);

        return pager;
    }

    @Override
    public Page<T> search(Pageable<List<MSearchRequest>> pageable, Class<T> tClass, String indexName) {
        Page<T> pager = new Page<>();

        SearchSourceBuilder builder = this.sourceBuilder(pageable);
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            pager.setTotal((int) hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        pager.setNo(pageable.getNo());
        pager.setSize(pageable.getSize());
        pager.setList(result);

        return pager;
    }

    @Override
    public Page<T> search(Pageable<List<MSearchRequest>> pageable, List<MSearchRequest> shoulds, Class<T> tClass,
                          String indexName,
                          String[] includeField,
                          String[] excludeField) {
        if (shoulds == null || shoulds.size() == 0) {
            return this.search(pageable, tClass, indexName, includeField, excludeField);
        }

        Page<T> pager = new Page<>();

        SearchSourceBuilder builder = this.sourceBuilder(pageable, shoulds);
        builder.fetchSource(includeField, excludeField);
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            pager.setTotal((int) hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        pager.setNo(pageable.getNo());
        pager.setSize(pageable.getSize());
        pager.setList(result);

        return pager;
    }

    @Override
    public Page<T> searchByNestedGroup(Pageable<List<MSearchRequest>> pageable, List<List<MSearchRequest>> nestedMustGroup, List<List<MSearchRequest>> shoulds,
                                       Class<T> tClass, String indexName, String[] includeField, String[] excludeField) {
        Page<T> pager = new Page<>();
//        if(shoulds != null && shoulds.size() == 0) {
        SearchSourceBuilder builder = this.sourceNestedGroupBuilder(pageable, nestedMustGroup, shoulds);
        builder.fetchSource(includeField, excludeField);
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            pager.setTotal((int) hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        pager.setNo(pageable.getNo());
        pager.setSize(pageable.getSize());
        pager.setList(result);

        return pager;
//        }
    }

    @Override
    public Page<T> searchShouldByGroup(Pageable<List<MSearchRequest>> pageable, List<List<MSearchRequest>> shoulds,
                                       Class<T> tClass, String indexName, String[] includeField, String[] excludeField) {
        if (shoulds == null || shoulds.size() == 0) {
            return this.search(pageable, tClass, indexName, includeField, excludeField);
        }

        Page<T> pager = new Page<>();

        SearchSourceBuilder builder = this.sourceGroupBuilder(pageable, shoulds);
        builder.fetchSource(includeField, excludeField);
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            pager.setTotal((int) hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        pager.setNo(pageable.getNo());
        pager.setSize(pageable.getSize());
        pager.setList(result);

        return pager;
    }

    @Override
    public Page<T> search(MFunctionScore option, Class<T> tClass) {
        Page<T> pager = new Page<>();
        SearchSourceBuilder builder = sourceBuilder(option);
        SearchRequest searchRequest = new SearchRequest(option.getIndexName());
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            pager.setTotal((int) hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        pager.setNo(option.getPageable().getNo());
        pager.setSize(option.getPageable().getSize());
        pager.setList(result);

        return pager;
    }

    @Override
    public HighlightResponse highlightSearch(Pageable<List<MSearchRequest>> pageable, HighlightBuilder highlightBuilder,
                                             String[] includeField, String indexName) {
        HighlightResponse hlRep = new HighlightResponse();

        SearchSourceBuilder builder = sourceBuilder(pageable);
        builder.highlighter(highlightBuilder);
        builder.fetchSource(includeField, null);

        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<HighlightResult> hlresult = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            hlRep.setTotal(hits.getTotalHits().value);
            for (SearchHit hit : hits) {
                HighlightResult highlightResult = new HighlightResult();
                highlightResult.setSources(hit.getSourceAsMap());
                highlightResult.setHighlightFields(hit.getHighlightFields());

                hlresult.add(highlightResult);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        hlRep.setResults(hlresult);
        hlRep.setNo(pageable.getNo());
        hlRep.setSize(pageable.getSize());

        return hlRep;
    }

    @Override
    public Map<String, Object> search(Pageable<List<MSearchRequest>> pageable) {
        throw new NotImplementedException();
    }

    @Override
    public List<T> search(List<MSearchRequest> pageable, Class<T> tClass) {
        throw new NotImplementedException();
    }

    @Override
    public List<T> search(MSearchRequest c, Class<T> tClass, String indexName) {
        List<MSearchRequest> requests = new ArrayList<>();
        requests.add(c);
        SearchSourceBuilder builder = this.sourceBuilder(requests);
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(builder);

        List<T> result = new ArrayList<>();
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = response.getHits();
            for (SearchHit hit : hits) {
                Map<String, Object> map = hit.getSourceAsMap();
                T t = BeanUtils.mapToBean(tClass, map);
                result.add(t);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    private void createQueryBuilder(List<MSearchRequest> c,
                                    List<NestedQueryBuilder> nestedQueryBuilderList,
                                    List<QueryBuilder> parentQueryBuilderList) {
        Map<String, List<MSearchRequest>> nestedRequestMap = new HashMap<>();
        for (MSearchRequest request : c) {
            if (request.getPath() == null) {
                parentQueryBuilderList.add(this.builder(request));
                continue;
            }
            //如果同一个宽表属性有多个条件
            if (!nestedRequestMap.containsKey(request.getPath())) {
                nestedRequestMap.put(request.getPath(), new ArrayList<>());
            }
            List<MSearchRequest> requestList = nestedRequestMap.get(request.getPath());
            requestList.add(request);
            nestedRequestMap.put(request.getPath(), requestList);
        }

        //宽表多条件处理
        if (nestedRequestMap.size() > 0) {
            StringBuffer sb = new StringBuffer();
            Iterator<Map.Entry<String, List<MSearchRequest>>> iterator = nestedRequestMap.entrySet().iterator();
            while (iterator.hasNext()) {
                NestedQueryBuilder nestedQueryBuilder = null;
                List<MSearchRequest> nestedGroup = iterator.next().getValue();

                for (MSearchRequest request : nestedGroup) {
                    sb.setLength(0);
                    sb.append(request.getPath());
                    sb.append(".");
                    sb.append(request.getFieldName());

                    nestedQueryBuilder = QueryBuilders.nestedQuery(request.getPath(),
                            this.builder(request, sb.toString()),
                            ScoreMode.Max);

                    nestedQueryBuilder.ignoreUnmapped(true);
                    nestedQueryBuilderList.add(nestedQueryBuilder);
                }
            }
        }
//        if (nestedRequestMap.size() > 0) {
//            StringBuffer sb = new StringBuffer();
//            Iterator<Map.Entry<String, List<MSearchRequest>>> iterator = nestedRequestMap.entrySet().iterator();
//            while (iterator.hasNext()) {
//                NestedQueryBuilder nestedQueryBuilder = null;
//                List<MSearchRequest> nestedGroup = iterator.next().getValue();
//                if (nestedGroup.size() > 1) {
//                    BoolQueryBuilder nestedBool = QueryBuilders.boolQuery();
//                    for (MSearchRequest request : nestedGroup) {
//                        sb.setLength(0);
//                        sb.append(request.getPath());
//                        sb.append(".");
//                        sb.append(request.getFieldName());
//
//                        nestedBool.must(this.builder(request, sb.toString()));
//                    }
//
//                    nestedQueryBuilder = QueryBuilders.nestedQuery(nestedGroup.get(0).getPath(),
//                            nestedBool,
//                            ScoreMode.Max);
//                } else {
//                    MSearchRequest nestedOne = nestedGroup.get(0);
//                    sb.setLength(0);
//                    sb.append(nestedOne.getPath());
//                    sb.append(".");
//                    sb.append(nestedOne.getFieldName());
//
//                    nestedQueryBuilder = QueryBuilders.nestedQuery(nestedOne.getPath(),
//                            this.builder(nestedOne, sb.toString()),
//                            ScoreMode.Max);
//                }
//
//                nestedQueryBuilder.ignoreUnmapped(true);
//                nestedQueryBuilderList.add(nestedQueryBuilder);
//            }
//        }
    }

    private void createGroupQueryBuilder(List<List<MSearchRequest>> requestGroups, List<NestedQueryBuilder> groupNestedQueryBuilderList, List<QueryBuilder> groupQueryBuilderList) {
        //先处理非宽表字段
        for (List<MSearchRequest> requestGroup : requestGroups) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            if (requestGroup.size() == 1) {
                MSearchRequest request = requestGroup.get(0);
                if (request.getPath() == null) {
                    groupQueryBuilderList.add(this.builder(request));
                }
            } else {
                for (MSearchRequest request : requestGroup) {
                    if (request.getPath() == null) {
                        boolQueryBuilder.must(this.builder(request));
                    }
                }
                if (boolQueryBuilder.must().size() > 0) {
                    groupQueryBuilderList.add(boolQueryBuilder);
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        for (List<MSearchRequest> requestGroup : requestGroups) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            String path = null;
            if (requestGroup.size() == 1) {
                MSearchRequest request = requestGroup.get(0);
                if (request.getPath() == null) {
                    continue;
                }
                sb.setLength(0);
                sb.append(request.getPath());
                sb.append(".");
                sb.append(request.getFieldName());
                groupNestedQueryBuilderList.add(QueryBuilders.nestedQuery(request.getPath(), this.builder(request, sb.toString()), ScoreMode.Max).ignoreUnmapped(true));
            } else {
                for (MSearchRequest request : requestGroup) {
                    if (request.getPath() == null) {
                        continue;
                    }
                    path = request.getPath();
                    sb.setLength(0);
                    sb.append(request.getPath());
                    sb.append(".");
                    sb.append(request.getFieldName());
                    boolQueryBuilder.must(this.builder(request, sb.toString()));
                }
                groupNestedQueryBuilderList.add(QueryBuilders.nestedQuery(path, boolQueryBuilder, ScoreMode.Max).ignoreUnmapped(true));
            }
        }
    }

    private SearchSourceBuilder sourceBuilder(List<MSearchRequest> requests) {
        List<NestedQueryBuilder> nestedQueryBuilderList = new ArrayList<>();
        List<QueryBuilder> parentQueryBuilderList = new ArrayList<>();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        createQueryBuilder(requests, nestedQueryBuilderList, parentQueryBuilderList);

        for (NestedQueryBuilder qbuilder : nestedQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        for (QueryBuilder qbuilder : parentQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.trackTotalHits(true);
        if (boolQuery.must().size() > 0) {
            builder.query(boolQuery);
        }

        return builder;
    }

    private SearchSourceBuilder sourceBuilder(Pageable<List<MSearchRequest>> pageable) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        List<NestedQueryBuilder> nestedQueryBuilderList = new ArrayList<>();
        List<QueryBuilder> parentQueryBuilderList = new ArrayList<>();

        createQueryBuilder(pageable.getT(), nestedQueryBuilderList, parentQueryBuilderList);

        for (NestedQueryBuilder qbuilder : nestedQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        for (QueryBuilder qbuilder : parentQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.trackTotalHits(true);
        if (boolQuery.must().size() > 0) {
            builder.query(boolQuery);
        }
        if (pageable.getNo() <= 0) {
            pageable.setNo(1);
        }
        if (pageable.getSize() <= 0) {
            pageable.setSize(20);
        }
        builder.from((pageable.getNo() - 1) * pageable.getSize());
        builder.size(pageable.getSize());

        for (String key : pageable.getSorts()) {
            builder.sort(key, pageable.getOrder());
        }

        return builder;
    }

    private SearchSourceBuilder sourceBuilder(Pageable<List<MSearchRequest>> pageable, List<MSearchRequest> shoulds) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        List<NestedQueryBuilder> should_NestedQueryBuilderList = new ArrayList<>();
        List<QueryBuilder> should_ParentQueryBuilderList = new ArrayList<>();

        createQueryBuilder(shoulds, should_NestedQueryBuilderList, should_ParentQueryBuilderList);

        for (NestedQueryBuilder qbuilder : should_NestedQueryBuilderList) {
            boolQuery.should(qbuilder);
        }
        for (QueryBuilder qbuilder : should_ParentQueryBuilderList) {
            boolQuery.should(qbuilder);
        }
        if (boolQuery.should().size() > 0) {
            boolQuery.minimumShouldMatch(1);
        }

        List<NestedQueryBuilder> nestedQueryBuilderList = new ArrayList<>();
        List<QueryBuilder> parentQueryBuilderList = new ArrayList<>();
        createQueryBuilder(pageable.getT(), nestedQueryBuilderList, parentQueryBuilderList);

        for (NestedQueryBuilder qbuilder : nestedQueryBuilderList) {
            boolQuery.must(qbuilder);
        }
        for (QueryBuilder qbuilder : parentQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.trackTotalHits(true);
//        if(boolQuery.must().size() > 0) {
        builder.query(boolQuery);
//        }
        if (pageable.getNo() <= 0) {
            pageable.setNo(1);
        }
        if (pageable.getSize() <= 0) {
            pageable.setSize(20);
        }
        builder.from((pageable.getNo() - 1) * pageable.getSize());
        builder.size(pageable.getSize());

        for (String key : pageable.getSorts()) {
            builder.sort(key, pageable.getOrder());
        }

        return builder;
    }

    private SearchSourceBuilder sourceNestedGroupBuilder(Pageable<List<MSearchRequest>> pageable, List<List<MSearchRequest>> nestedMustGroup, List<List<MSearchRequest>> shoulds) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        List<NestedQueryBuilder> groupNestedQueryBuilderList;
        List<QueryBuilder> groupQueryBuilderList;
        if (shoulds != null && shoulds.size() > 0) {
            groupNestedQueryBuilderList = new ArrayList<>();
            groupQueryBuilderList = new ArrayList<>();
            BoolQueryBuilder shouldBoolQuery = new BoolQueryBuilder();
            createGroupQueryBuilder(shoulds, groupNestedQueryBuilderList, groupQueryBuilderList);
            for (NestedQueryBuilder nestedQueryBuilder : groupNestedQueryBuilderList) {
                shouldBoolQuery.should(nestedQueryBuilder);
            }
            for (QueryBuilder queryBuilder : groupQueryBuilderList) {
                shouldBoolQuery.should(queryBuilder);
            }
            boolQuery.must(shouldBoolQuery);
        }

        if (nestedMustGroup != null && nestedMustGroup.size() > 0) {
            groupNestedQueryBuilderList = new ArrayList<>();
            groupQueryBuilderList = new ArrayList<>();
            createGroupQueryBuilder(nestedMustGroup, groupNestedQueryBuilderList, groupQueryBuilderList);
            for (NestedQueryBuilder nestedQueryBuilder : groupNestedQueryBuilderList) {
                boolQuery.must(nestedQueryBuilder);
            }
            for (QueryBuilder queryBuilder : groupQueryBuilderList) {
                boolQuery.must(queryBuilder);
            }
        }

        List<NestedQueryBuilder> nestedQueryBuilderList = new ArrayList<>();
        List<QueryBuilder> parentQueryBuilderList = new ArrayList<>();
        createQueryBuilder(pageable.getT(), nestedQueryBuilderList, parentQueryBuilderList);

        for (NestedQueryBuilder qbuilder : nestedQueryBuilderList) {
            boolQuery.must(qbuilder);
        }
        for (QueryBuilder qbuilder : parentQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.trackTotalHits(true);
//        if(boolQuery.must().size() > 0) {
        builder.query(boolQuery);
//        }
        if (pageable.getNo() <= 0) {
            pageable.setNo(1);
        }
        if (pageable.getSize() <= 0) {
            pageable.setSize(20);
        }
        builder.from((pageable.getNo() - 1) * pageable.getSize());
        builder.size(pageable.getSize());

        for (String key : pageable.getSorts()) {
            builder.sort(key, pageable.getOrder());
        }

        return builder;
    }

    private SearchSourceBuilder sourceGroupBuilder(Pageable<List<MSearchRequest>> pageable, List<List<MSearchRequest>> shoulds) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (shoulds != null && shoulds.size() > 0) {
            BoolQueryBuilder shouldBoolQuery = new BoolQueryBuilder();
            List<NestedQueryBuilder> should_NestedQueryBuilderList = new ArrayList<>();
            List<QueryBuilder> groupQueryBuilderList = new ArrayList<>();
            createGroupQueryBuilder(shoulds, should_NestedQueryBuilderList, groupQueryBuilderList);
            for (NestedQueryBuilder queryBuilder : should_NestedQueryBuilderList) {
                shouldBoolQuery.should(queryBuilder);
            }
            for (QueryBuilder queryBuilder : groupQueryBuilderList) {
                shouldBoolQuery.should(queryBuilder);
            }
            boolQuery.must(shouldBoolQuery);
        }

        List<NestedQueryBuilder> nestedQueryBuilderList = new ArrayList<>();
        List<QueryBuilder> parentQueryBuilderList = new ArrayList<>();
        createQueryBuilder(pageable.getT(), nestedQueryBuilderList, parentQueryBuilderList);

        for (NestedQueryBuilder qbuilder : nestedQueryBuilderList) {
            boolQuery.must(qbuilder);
        }
        for (QueryBuilder qbuilder : parentQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.trackTotalHits(true);
//        if(boolQuery.must().size() > 0) {
        builder.query(boolQuery);
//        }
        if (pageable.getNo() <= 0) {
            pageable.setNo(1);
        }
        if (pageable.getSize() <= 0) {
            pageable.setSize(20);
        }
        builder.from((pageable.getNo() - 1) * pageable.getSize());
        builder.size(pageable.getSize());

        for (String key : pageable.getSorts()) {
            builder.sort(key, pageable.getOrder());
        }

        return builder;
    }

    private SearchSourceBuilder sourceBuilder(MFunctionScore option) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (option.getShoulds() != null && option.getShoulds().size() > 0) {
            List<NestedQueryBuilder> should_NestedQueryBuilderList = new ArrayList<>();
            List<QueryBuilder> should_ParentQueryBuilderList = new ArrayList<>();
            createQueryBuilder(option.getShoulds(), should_NestedQueryBuilderList, should_ParentQueryBuilderList);
            for (NestedQueryBuilder qbuilder : should_NestedQueryBuilderList) {
                boolQuery.should(qbuilder);
            }
            for (QueryBuilder qbuilder : should_ParentQueryBuilderList) {
                boolQuery.should(qbuilder);
            }
            if (boolQuery.should().size() > 0) {
                boolQuery.minimumShouldMatch(1);
            }
        } else if (option.getShoulds2() != null && option.getShoulds2().size() > 0) {
            BoolQueryBuilder shouldBoolQuery = new BoolQueryBuilder();
            List<NestedQueryBuilder> should_NestedQueryBuilderList = new ArrayList<>();
            List<QueryBuilder> groupQueryBuilderList = new ArrayList<>();
            createGroupQueryBuilder(option.getShoulds2(), should_NestedQueryBuilderList, groupQueryBuilderList);
            for (NestedQueryBuilder queryBuilder : should_NestedQueryBuilderList) {
                shouldBoolQuery.should(queryBuilder);
            }
            for (QueryBuilder queryBuilder : groupQueryBuilderList) {
                shouldBoolQuery.should(queryBuilder);
            }
            boolQuery.must(shouldBoolQuery);
        }

        List<NestedQueryBuilder> nestedQueryBuilderList = new ArrayList<>();
        List<QueryBuilder> parentQueryBuilderList = new ArrayList<>();
        createQueryBuilder(option.getPageable().getT(), nestedQueryBuilderList, parentQueryBuilderList);

        for (NestedQueryBuilder qbuilder : nestedQueryBuilderList) {
            boolQuery.must(qbuilder);
        }
        for (QueryBuilder qbuilder : parentQueryBuilderList) {
            boolQuery.must(qbuilder);
        }

        FunctionScoreQueryBuilder func = QueryBuilders.functionScoreQuery(this.builder(option.getFunRequest()),
                ScoreFunctionBuilders.scriptFunction(new Script(option.getScript())));
        func.boostMode(option.getMode());

        boolQuery.must(func);

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.trackTotalHits(true);
        if (boolQuery.must().size() > 0) {
            builder.query(boolQuery);
        }
        if (option.getPageable().getNo() <= 0) {
            option.getPageable().setNo(1);
        }
        if (option.getPageable().getSize() <= 0) {
            option.getPageable().setSize(20);
        }
        builder.from((option.getPageable().getNo() - 1) * option.getPageable().getSize());
        builder.size(option.getPageable().getSize());

        for (String key : option.getPageable().getSorts()) {
            builder.sort(key, option.getPageable().getOrder());
        }

        return builder;
    }

    private QueryBuilder builder(MSearchRequest request, String childKeyName) {
        QueryBuilder queryBuilder = null;
        if (request.getMatchType() == MatchType.match) {
            queryBuilder = QueryBuilders.matchQuery(childKeyName, request.getValue()).operator(request.getOperator());
        } else if (request.getMatchType() == MatchType.match_phrase) {
            queryBuilder = QueryBuilders.matchPhraseQuery(childKeyName, request.getValue());
        } else if (request.getMatchType() == MatchType.match_phrase_prefix) {
            queryBuilder = QueryBuilders.matchPhrasePrefixQuery(childKeyName, request.getValue());
        } else if (request.getMatchType() == MatchType.multi_match) {
            queryBuilder = QueryBuilders.multiMatchQuery(request.getValue(), request.getFieldNames()).type(request.getMulti_type());
        } else if (request.getMatchType() == MatchType.term) {
            queryBuilder = QueryBuilders.termQuery(childKeyName, request.getValue());
        } else if (request.getMatchType() == MatchType.terms) {
            queryBuilder = QueryBuilders.termsQuery(childKeyName, (Collection<?>) request.getValue());
        } else if (request.getMatchType() == MatchType.range) {
            RangeQueryBuilder range;
            if (request.getValue() == null && request.getParm1() != null) {
                range = QueryBuilders.rangeQuery(childKeyName).lte(request.getParm1());
            } else {
                range = QueryBuilders.rangeQuery(childKeyName).gte(request.getValue());
                if (request.getParm1() != null) {
                    range.lte(request.getParm1());
                }
            }
            queryBuilder = range;
        } else if (request.getMatchType() == MatchType.range_sec) {
            RangeQueryBuilder range;
            if (request.getValue() == null && request.getParm1() != null) {
                range = QueryBuilders.rangeQuery(childKeyName).format("epoch_second").
                        lte(request.getParm1());
            } else {
                range = QueryBuilders.rangeQuery(childKeyName).format("epoch_second").
                        gte(request.getValue());
                if (request.getParm1() != null) {
                    range.lte(request.getParm1());
                }
            }
            queryBuilder = range;
        } else if (request.getMatchType() == MatchType.range_ms) {
            RangeQueryBuilder range;
            if (request.getValue() == null && request.getParm1() != null) {
                range = QueryBuilders.rangeQuery(childKeyName).format("epoch_millis").
                        lte(request.getParm1());
            } else {
                range = QueryBuilders.rangeQuery(childKeyName).format("epoch_millis").
                        gte(request.getValue());
                if (request.getParm1() != null) {
                    range.lte(request.getParm1());
                }
            }
            queryBuilder = range;
        } else if (request.getMatchType() == MatchType.range_ymd) {
            RangeQueryBuilder range;
            if (request.getValue() == null && request.getParm1() != null) {
                range = QueryBuilders.rangeQuery(childKeyName).format("yyyy-MM-dd").
                        lte(request.getParm1());
            } else {
                range = QueryBuilders.rangeQuery(childKeyName).format("yyyy-MM-dd").
                        gte(request.getValue());
                if (request.getParm1() != null) {
                    range.lte(request.getParm1());
                }
            }
            queryBuilder = range;
        }

        if (request.getWeight() > 0) {
            queryBuilder.boost(request.getWeight());
        }

        return queryBuilder;
    }

    private QueryBuilder builder(MSearchRequest request) {
        return this.builder(request, request.getFieldName());
    }

    @Override
    public BulkResponse bulk(BulkRequest bulkRequest, RequestOptions options) {
        try {
            return restHighLevelClient.bulk(bulkRequest, options);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
